// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{fmt::Display, str::FromStr};

use nom::{branch::alt, bytes::complete::tag, combinator::{map, opt}, IResult};
use serde::{de::Visitor, Deserialize, Serialize};

use crate::{Version, VersionParseError};

use super::version::Inner;

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct VersionReq {
    pub op: VersionOp,
    pub version: Option<Version>,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum VersionOp {
    Exact,
    Greater,
    GreaterEq,
    Less,
    LessEq,
    /// Tilde requirements allow the patch part of the version (the third number) to increase.
    Tilde,
    /// Caret requirements allow parts that are right of the first nonzero part of the version to increase.
    Caret,
    #[default]
    Wildcard,
}

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum VersionReqParseError {
    #[error("bad op")]
    BadOp,
    #[error("bad version")]
    BadVersion(#[from] VersionParseError),
    #[error("op is not wildcard and no version given")]
    NoVersion,
}

impl Display for VersionReq {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.version.as_ref() {
            Some(version) => write!(f, "{}{}", self.op, version),
            None => self.op.fmt(f),
        }
    }
}

impl Display for VersionOp {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.as_str().fmt(f)
    }
}

impl Serialize for VersionReq {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de> Deserialize<'de> for VersionReq {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>
    {
        struct VersionReqVisitor;

        impl Visitor<'_> for VersionReqVisitor {
            type Value = VersionReq;
        
            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("version req")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
                where
                    E: serde::de::Error,
            {
                VersionReq::from_str(v)
                    .map_err(serde::de::Error::custom)
            }
        }

        deserializer.deserialize_str(VersionReqVisitor)
    }
}

impl VersionReq {
    pub fn new(op: VersionOp, version: Version) -> Self {
        Self {
            op,
            version: Some(version),
        }
    }

    fn from_op(op: VersionOp) -> Self {
        Self::from(op)
    }

    pub fn all() -> Self {
        Self::from_op(VersionOp::Wildcard)
    }

    pub fn matches(&self, version: &Version) -> bool {
        match self.op {
            VersionOp::Exact => self.version.as_ref().unwrap() == version,
            VersionOp::Greater => version > self.version.as_ref().unwrap(),
            VersionOp::GreaterEq => version >= self.version.as_ref().unwrap(),
            VersionOp::Less => version < self.version.as_ref().unwrap(),
            VersionOp::LessEq => version <= self.version.as_ref().unwrap(),
            VersionOp::Tilde => Self::match_tilde(&version.inner, &self.version.as_ref().unwrap().inner),
            VersionOp::Caret => Self::match_caret(&version.inner, &self.version.as_ref().unwrap().inner),
            VersionOp::Wildcard => true,
        }
    }

    fn match_tilde(v1: &Inner, v2: &Inner) -> bool {
        v1.major == v2.major
        && (v1.minor.is_none() || v2.minor.is_none() || v1.minor == v2.minor)
        && (v1.patch.is_none() || v2.patch.is_none() || v1.patch >= v2.patch)
    }

    fn match_caret(v1: &Inner, v2: &Inner) -> bool {
        v1.major == v2.major
        && (v1.minor.is_none() || v2.minor.is_none() 
            || (v1.major == 0 && v1.minor == v2.minor) || (v1.major != 0 && v1.minor >= v2.minor))
        && (v1.patch.is_none() || v2.patch.is_none()
            || (v1.minor == Some(0) && v1.patch == v2.patch) || (v1.minor != Some(0) && v1.patch >= v2.patch))
    }
}

impl VersionOp {
    pub fn as_str(&self) -> &str {
        match self {
            VersionOp::Exact => "=",
            VersionOp::Greater => ">",
            VersionOp::GreaterEq => ">=",
            VersionOp::Less => "<",
            VersionOp::LessEq => "<=",
            VersionOp::Tilde => "~",
            VersionOp::Caret => "^",
            VersionOp::Wildcard => "*",
        }
    }

    pub fn parse(i: &str) -> IResult<&str, Self> {
        alt((
            map(tag("="), |_| Self::Exact),
            map(tag(">="), |_| Self::GreaterEq),
            map(tag(">"), |_| Self::Greater),
            map(tag("<="), |_| Self::LessEq),
            map(tag("<"), |_| Self::Less),
            map(tag("~"), |_| Self::Tilde),
            map(tag("^"), |_| Self::Caret),
            map(tag("*"), |_| Self::Wildcard),
        ))(i)
    }
}

impl FromStr for VersionReq {
    type Err = VersionReqParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (i, op) = opt(VersionOp::parse)(s)
            .map_err(|_| VersionReqParseError::BadOp)?;
        let version = if i.is_empty() && !s.is_empty() {
            if let Some(op) = op {
                if op != VersionOp::Wildcard {
                    return Err(VersionReqParseError::NoVersion);
                }
            }
            None
        } else {
            Some(Version::from_str(i)?)
        };
        Ok(Self {
            op: op.unwrap_or(VersionOp::Exact),
            version,
        })
    }
}

impl FromStr for VersionOp {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "=" => Ok(Self::Exact),
            ">" => Ok(Self::Greater),
            ">=" => Ok(Self::GreaterEq),
            "<" => Ok(Self::Less),
            "<=" => Ok(Self::LessEq),
            "~" => Ok(Self::Tilde),
            "^" => Ok(Self::Caret),
            "*" => Ok(Self::Wildcard),
            _ => Err(())
        }
    }
}

impl From<VersionOp> for VersionReq {
    fn from(op: VersionOp) -> Self {
        Self {
            op,
            version: None,
        }
    }
}

#[cfg(test)]
mod test {
    use std::str::FromStr;

    use crate::{Version, VersionOp, VersionParseError, VersionReq, VersionReqParseError};

    #[test]
    fn test_version_req_from_str() {
        assert_eq!(
            VersionReq::from_str(""),
            Err(VersionReqParseError::BadVersion(VersionParseError::BadMajor))
        );
        assert_eq!(
            VersionReq::from_str("="),
            Err(VersionReqParseError::NoVersion)
        );
        assert_eq!(
            VersionReq::from_str("*"),
            Ok(VersionReq::from_op(VersionOp::Wildcard))
        );
        assert_eq!(
            VersionReq::from_str("=1.2.3"),
            Ok(VersionReq::new(VersionOp::Exact, Version::from_standard(1,2,3)))
        );
        assert_eq!(
            VersionReq::from_str("1.2.3"),
            Ok(VersionReq::new(VersionOp::Exact, Version::from_standard(1,2,3)))
        );
    }

    #[test]
    fn test_version_req_serialize() {
        let mut s = String::new();
        serde::Serialize::serialize(
            &VersionReq::new(
                VersionOp::Greater,
                Version::from_standard(1, 2, 3)
            ),
            toml::ser::ValueSerializer::new(&mut s)
        ).unwrap();

        assert_eq!(
            &s,
            "\">1.2.3\""
        );
    }

    #[test]
    fn test_version_req_deserialize() {
        assert_eq!(
            serde::Deserialize::deserialize(toml::de::ValueDeserializer::new("\">1.2.3\"")),
            Ok(VersionReq::new(
                VersionOp::Greater,
                Version::from_standard(1, 2, 3)
            ))
        );
        assert_eq!(
            serde::Deserialize::deserialize(toml::de::ValueDeserializer::new("\">=1.2.3\"")),
            Ok(VersionReq::new(
                VersionOp::GreaterEq,
                Version::from_standard(1, 2, 3)
            ))
        );
    }

    #[test]
    fn test_version_req_matches() {
        assert!(VersionReq::all().matches(&Version::from_major(1)));
        assert!(VersionReq::all().matches(&Version::from_major(2)));

        assert!(VersionReq::new(VersionOp::Exact, Version::from_major(1)).matches(&Version::from_major(1)));
        assert!(VersionReq::new(VersionOp::Exact, Version::from_major(1)).matches(&Version::from_standard(1, 2, 3)));
    
        assert!(VersionReq::new(VersionOp::Greater, Version::from_major(1)).matches(&Version::from_major(2)));
        assert!(VersionReq::new(VersionOp::GreaterEq, Version::from_standard(2, 0, 0)).matches(&Version::from_major(2)));
    
        assert!(VersionReq::new(VersionOp::Tilde, Version::from_major(1)).matches(&Version::from_major(1)));
        assert!(VersionReq::new(VersionOp::Tilde, Version::from_major(1)).matches(&Version::from_standard(1, 2, 3)));
        assert!(VersionReq::new(VersionOp::Tilde, Version::from_major_minor(1, 2)).matches(&Version::from_standard(1, 2, 3)));
        assert!(VersionReq::new(VersionOp::Tilde, Version::from_major_minor(1, 2)).matches(&Version::from_major(1)));
        assert!(VersionReq::new(VersionOp::Tilde, Version::from_standard(1, 2, 3)).matches(&Version::from_standard(1, 2, 4)));
        assert!(!VersionReq::new(VersionOp::Tilde, Version::from_standard(1, 2, 3)).matches(&Version::from_standard(1, 2, 2)));
    
        assert!(VersionReq::new(VersionOp::Caret, Version::from_standard(1, 2, 3)).matches(&Version::from_major(1)));
        assert!(VersionReq::new(VersionOp::Caret, Version::from_standard(1, 2, 3)).matches(&Version::from_standard(1, 2, 4)));
        assert!(VersionReq::new(VersionOp::Caret, Version::from_standard(1, 2, 3)).matches(&Version::from_standard(1, 3, 4)));
        assert!(!VersionReq::new(VersionOp::Caret, Version::from_standard(1, 2, 3)).matches(&Version::from_standard(1, 2, 2)));
        assert!(VersionReq::new(VersionOp::Caret, Version::from_standard(0, 2, 3)).matches(&Version::from_standard(0, 2, 4)));
        assert!(!VersionReq::new(VersionOp::Caret, Version::from_standard(0, 2, 3)).matches(&Version::from_standard(0, 3, 4)));
        assert!(VersionReq::new(VersionOp::Caret, Version::from_standard(0, 0, 1)).matches(&Version::from_standard(0, 0, 1)));
        assert!(!VersionReq::new(VersionOp::Caret, Version::from_standard(0, 0, 1)).matches(&Version::from_standard(0, 0, 2)));
        assert!(VersionReq::new(VersionOp::Caret, Version::from_major_minor(0, 0)).matches(&Version::from_standard(0, 0, 2)));
    }
}